﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Xml.Linq;

using ProjectBase.Core;
using ProjectBase.Tools.Publishing.Handlers.ExceptionHandler;
using Constants = ProjectBase.Tools.Publishing.Handlers.Constants;

namespace ProjectBase.Tools.Publishing
{
    public class ExceptionPublishingHandler : PublishingHandlerBase
    {
        #region Init
        public override void Init(XElement settings)
        {
            var exceptionsConfigPath = FindElement(settings, Constants.ElementExceptionsConfigPath, Constants.DefaultExceptionsConfigPath);
            var applicationName = FindElement(settings, Constants.ElementApplicationName, Constants.DefaultApplicationName);
            var logName = FindElement(settings, Constants.ElementLogName, Constants.DefaultLogName);

            var reader = new ConfigurationReader();
            var configuration = reader.Create(exceptionsConfigPath);

            if (settings.Is())
            {
                configuration = reader.SetDefaults(settings, configuration);
            }

            Init(applicationName, logName, configuration);
        }
        public virtual void Init(string applicationName, string logName, ExceptionConfiguration configuration)
        {
            Init(applicationName, logName, configuration, null);
        }
        public virtual void Init(string applicationName, string logName, ExceptionConfiguration configuration, ExceptionStringBuilder builder)
        {
            Contract.Requires(applicationName.IsNotEmpty(), "ExceptionPublishingHandler: parameter 'applicationName' cannot be null nor empty");
            Contract.Requires(logName.IsNotEmpty(), "ExceptionPublishingHandler: parameter 'logName' cannot be null nor empty");
            Contract.Requires(configuration.Is(), "ExceptionPublishingHandler: parameter 'configuration' cannot be null");

            ApplicationName = applicationName;
            LogName = logName;
            Configuration = configuration;
            Builder = builder ?? new ExceptionStringBuilder();

            if (!EventLog.SourceExists(ApplicationName))
            {
                EventLog.CreateEventSource(ApplicationName, LogName);
            }
        }
        #endregion Init

        #region Publish
        protected override PublishingResult Publish(object reporter, string message, Exception exception)
        {
            if (Configuration.DoNotPublish)
            {
                return PublishingResult.Continue;
            }
            if (exception.IsNull())
            {
                return PublishingResult.Continue;
            }

            var eventLogEntryType = ExceptionToEntryType(exception);

            if (eventLogEntryType.Is())
            {
                var entryMessage = ExceptionToString(exception);
                var eventId = ExceptionToEventId(exception);
                EventLog.WriteEntry(ApplicationName, entryMessage, eventLogEntryType.Value, eventId);
            }

            return PublishingResult.Continue;
        }

        protected override PublishingResult Publish(object reporter, Severity severity, object message, params object[] args)
        {
            var exception = message as Exception;
            if (exception.Is())
            {
                return Publish(reporter, null, exception);
            }
            return PublishingResult.Continue;
        }

        protected override PublishingResult Publish(object reporter, Func<string> message, Severity severity)
        {
            return PublishingResult.Continue;
        }
        #endregion Publish

        #region protected
        protected virtual string ExceptionToString(Exception exception)
        {
            return ExceptionToString(exception, null);
        }
        protected virtual string ExceptionToString(Exception exception, NameValueCollection additionalInfo)
        {
            return Builder.Build(exception, additionalInfo);
        }

        protected virtual EventLogEntryType? ExceptionToEntryType(Exception exception)
        {
            var setting = Configuration.FindSettings(exception);
            if (setting.Is())
            {
                return setting.EventLogEntryType;
            }
            return Configuration.DefaultEventLogEntryType;
        }

        protected virtual int ExceptionToEventId(Exception exception)
        {
            var setting = Configuration.FindSettings(exception);
            if (setting.Is())
            {
                return setting.EventId;
            }
            return Configuration.DefaultEventId;
        }
        protected string FindElement(XContainer settings, string name, string defaultValue)
        {
            Contract.Requires(defaultValue.IsNotEmpty());
            Contract.Requires(name.IsNotEmpty());

            var value = settings.Element(name);
            if (value.Is()
                && value.Attribute(Constants.AttributeValue).Is()
                && value.Attribute(Constants.AttributeValue).Value.IsNotEmpty())
            {
                return value.Attribute(Constants.AttributeValue).Value;
            }
            return defaultValue;
        }
        #endregion protected

        #region properties
        public virtual string ApplicationName { get; protected set; }
        public virtual string LogName { get; protected set; }
        public virtual ExceptionConfiguration Configuration { get; protected set; }
        public virtual ExceptionStringBuilder Builder { get; protected set; }
        #endregion properties
    }
}